package com.fleetdm.agent.scep

import com.fleetdm.agent.GetCertificateTemplateResponse
import org.bouncycastle.asn1.DERPrintableString
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
import org.jscep.client.Client
import org.jscep.client.verification.OptimisticCertificateVerifier
import java.math.BigInteger
import java.net.URL
import java.security.KeyPairGenerator
import java.security.Security
import java.security.cert.Certificate
import java.util.Date
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

/**
 * Implementation of ScepClient using jScep library and BouncyCastle cryptography.
 *
 * This implementation performs SCEP enrollment by:
 * 1. Generating an RSA key pair
 * 2. Creating a self-signed certificate for PKCS7 envelope signing
 * 3. Building a Certificate Signing Request (CSR) with challenge password
 * 4. Sending enrollment request to SCEP server
 * 5. Extracting and returning the issued certificate and private key
 */
class ScepClientImpl : ScepClient {

    companion object {
        private const val SCEP_PROFILE = "NDESCA" // Network Device Enrollment Service CA
        private const val SELF_SIGNED_CERT_VALIDITY_DAYS = 100L

        init {
            // Ensure BouncyCastle provider is loaded
            if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
                Security.addProvider(BouncyCastleProvider())
            }
        }
    }

    override suspend fun enroll(config: GetCertificateTemplateResponse): ScepResult = withContext(Dispatchers.IO) {
        try {
            // Log calls removed to avoid test failures on JVM (use logcat in Android Studio)

            // Step 1: Generate key pair
            val keyPair = generateKeyPair(config.keyLength)

            // Step 2: Parse subject name
            val entity = try {
                X500Name(config.subjectName)
            } catch (e: Exception) {
                throw ScepCsrException("Invalid X.500 subject name: ${config.subjectName}", e)
            }

            // Step 3: Create self-signed certificate for signing the PKCS7 envelope
            val selfSignedCert = createSelfSignedCertificate(
                entity,
                keyPair,
                config.signatureAlgorithm,
            )

            // Step 4: Create SCEP client
            val server = try {
                URL(config.url)
            } catch (e: Exception) {
                throw ScepNetworkException("Invalid SCEP URL: ${config.url}", e)
            }

            // OptimisticCertificateVerifier is used intentionally because:
            // 1. SCEP URL is provided by the authenticated MDM server
            // 2. Challenge password authenticates the enrollment request
            // 3. Enterprise SCEP servers often use internal CAs not in system trust stores
            // 4. The enrolled certificate itself is validated when used
            val verifier = OptimisticCertificateVerifier()
            val client = Client(server, verifier)

            // Step 5: Build Certificate Signing Request (CSR)
            val csr = buildCsr(entity, keyPair, config.scepChallenge, config.signatureAlgorithm)

            // Step 6: Send enrollment request
            val response = try {
                client.enrol(selfSignedCert, keyPair.private, csr, SCEP_PROFILE)
            } catch (e: Exception) {
                throw ScepNetworkException("Failed to communicate with SCEP server", e)
            }

            // Step 7: Process response
            when {
                response.isSuccess -> {
                    val certificates = extractCertificates(response.certStore)

                    if (certificates.isEmpty()) {
                        throw ScepCertificateException("No certificates returned from SCEP server")
                    }

                    ScepResult(
                        privateKey = keyPair.private,
                        certificateChain = certificates,
                    )
                }
                response.isPending -> {
                    throw ScepEnrollmentException(
                        "Enrollment is pending - requires CA administrator approval",
                    )
                }
                else -> {
                    throw ScepEnrollmentException(
                        "Enrollment failed - certificate not issued by SCEP server",
                    )
                }
            }
        } catch (e: ScepException) {
            // Re-throw ScepException as-is (Log.e removed to avoid test failures)
            throw e
        } catch (e: Exception) {
            // Wrap unexpected exceptions in ScepException (Log.e removed to avoid test failures)
            throw ScepException("Unexpected SCEP enrollment error: ${e.message}", e)
        }
    }

    private fun generateKeyPair(keyLength: Int) = try {
        val keyGen = KeyPairGenerator.getInstance("RSA")
        keyGen.initialize(keyLength)
        keyGen.genKeyPair()
    } catch (e: Exception) {
        throw ScepKeyGenerationException("Failed to generate RSA key pair", e)
    }

    private fun createSelfSignedCertificate(entity: X500Name, keyPair: java.security.KeyPair, signatureAlgorithm: String) = try {
        val now = System.currentTimeMillis()
        val validityEnd = now + (1000L * 60 * 60 * 24 * SELF_SIGNED_CERT_VALIDITY_DAYS)

        val certBuilder = JcaX509v3CertificateBuilder(
            entity,
            BigInteger.valueOf(1),
            Date(now),
            Date(validityEnd),
            entity,
            keyPair.public,
        )

        val contentSigner = JcaContentSignerBuilder(signatureAlgorithm).build(keyPair.private)
        val certHolder = certBuilder.build(contentSigner)

        JcaX509CertificateConverter().getCertificate(certHolder)
    } catch (e: Exception) {
        throw ScepCertificateException("Failed to create self-signed certificate", e)
    }

    private fun buildCsr(entity: X500Name, keyPair: java.security.KeyPair, challenge: String, signatureAlgorithm: String) = try {
        val csrBuilder = JcaPKCS10CertificationRequestBuilder(entity, keyPair.public)

        // Add challenge password attribute
        val passwordAttr = DERPrintableString(challenge)
        csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_challengePassword, passwordAttr)

        val contentSigner = JcaContentSignerBuilder(signatureAlgorithm).build(keyPair.private)
        csrBuilder.build(contentSigner)
    } catch (e: Exception) {
        throw ScepCsrException("Failed to build Certificate Signing Request", e)
    }

    private fun extractCertificates(certStore: java.security.cert.CertStore): List<Certificate> = try {
        val certificates = certStore.getCertificates(null)
        certificates.toList()
    } catch (e: Exception) {
        throw ScepCertificateException("Failed to extract certificates from response", e)
    }
}
